package com.zjhcsoft.auth.gateway.predicate;

import com.dc.eai.data.CompositeData;
import com.dc.eai.data.Field;
import com.dcfs.esb.utils.TLSimRepTools;
import com.zjhcsoft.auth.gateway.predicate.EsbReadBodyPredicateFactory.Config;
import java.util.List;
import java.util.function.Predicate;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.reactivestreams.Publisher;
import org.springframework.cloud.gateway.handler.AsyncPredicate;
import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.http.codec.HttpMessageReader;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.reactive.function.server.HandlerStrategies;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

/**
 * 自定义
 *
 * @author yuxg
 * @create 2021-04-02
 */
@Component
public class EsbReadBodyPredicateFactory extends AbstractRoutePredicateFactory<Config> {

  protected static final Log log = LogFactory.getLog(EsbReadBodyPredicateFactory.class);

  private static final String TEST_ATTRIBUTE = "read_body_predicate_test_attribute";

  private static final String CACHE_REQUEST_BODY_OBJECT_KEY = "cachedRequestBodyObject";

  private static final List<HttpMessageReader<?>> messageReaders = HandlerStrategies
      .withDefaults().messageReaders();

  public EsbReadBodyPredicateFactory() {
    super(EsbReadBodyPredicateFactory.Config.class);
  }

  @Override
  public AsyncPredicate<ServerWebExchange> applyAsync(EsbReadBodyPredicateFactory.Config config) {
    return new AsyncPredicate<ServerWebExchange>() {
      @Override
      public Publisher<Boolean> apply(ServerWebExchange exchange) {
        Object cachedBody = exchange.getAttribute(CACHE_REQUEST_BODY_OBJECT_KEY);
        Mono<?> modifiedBody;
        // We can only read the body from the request once, once that happens if
        // we try to read the body again an exception will be thrown. The below
        // if/else caches the body object as a request attribute in the
        // ServerWebExchange so if this filter is run more than once (due to more
        // than one route using it) we do not try to read the request body
        // multiple times
        if (cachedBody != null) {
          try {
            boolean test = decodeAndMatch(config.getCode(),(String) cachedBody);
            exchange.getAttributes().put(TEST_ATTRIBUTE, test);
            return Mono.just(test);
          } catch (ClassCastException e) {
            if (log.isDebugEnabled()) {
              log.debug("Predicate test failed because class in predicate "
                  + "does not match the cached body object", e);
            }
          }
          return Mono.just(false);
        } else {
          return ServerWebExchangeUtils.cacheRequestBodyAndRequest(exchange,
              serverHttpRequest -> ServerRequest
                  .create(exchange.mutate().request(serverHttpRequest).build(), messageReaders)
                  .bodyToMono(String.class)
                  .doOnNext(objectValue -> exchange.getAttributes().put(CACHE_REQUEST_BODY_OBJECT_KEY, objectValue))
                  .map(objectValue -> decodeAndMatch(config.code, objectValue)));
        }
      }

      @Override
      public String toString() {
        return String.format("ReadBody: %s", config.getCode());
      }
    };
  }

  @Override
  public Predicate<ServerWebExchange> apply(EsbReadBodyPredicateFactory.Config config) {
    throw new UnsupportedOperationException(
        "EsbReadBodyPredicateFactory is only async.");
  }

  /**
   * 解析并匹配路由
   * @param code ESB场景编码
   * @param xmlString ESB过来的报文
   * @return boolean
   */
  public boolean decodeAndMatch (String code, String xmlString) {
    if (StringUtils.isEmpty(code) || StringUtils.isEmpty(xmlString)) {
      return false;
    }
    CompositeData reqCD = TLSimRepTools.xmlToCD(xmlString.getBytes(), "UTF-8");
    if (reqCD != null) {
      CompositeData sysHead = reqCD.getStruct("SYS_HEAD");
      CompositeData appHead = reqCD.getStruct("APP_HEAD");//ESB报文应用头
      CompositeData body = reqCD.getStruct("BODY");//ESB报文体

      Field field_code = sysHead.getField("SERVICE_CODE");
      Field field_scene = sysHead.getField("SERVICE_SCENE");

      //ESB服务码
      String serviceCode = field_code.strValue();
      //ESB场景码
      String serviceScene = field_scene.strValue();

      StringBuilder sb = new StringBuilder();
      sb.append(serviceCode);
      sb.append(serviceScene);
      return code.equals(sb.toString());
    }
    return false;
  }


  public static class Config {

    private String code;

    public String getCode() {
      return code;
    }

    public void setCode(String code) {
      this.code = code;
    }
  }
}
